/*
 * (c) ralfoide, http://gamez.googlecode.com/, 2008
 * Project: gamez
 * License TBD
 */


package com.alfray.gamez.shape;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.opengles.GL10;

/**
 * Generic abstract implementation of a simple 3d sprite.
 * 
 * It is assumed this sprite uses the same color for all vertices and that
 * the geometry can be defined once at construction time (w/o any GL context).
 * The geometry is defined by triangles, specified in CW order.
 * 
 * Derived classes need to call setupBuffers once in their constructor.
 */
public abstract class Shape {

    /** Available colors */
    public enum Color {
        DEFAULT(0),
        MAGENTA(1),
        CYAN(2),
        BLUE(3),
        GREEN(4),
        YELLOW(5),
        ORANGE(6),
        RED(7),
        WHITE(8);

        private int mIndex;
        Color(int index) {
            mIndex = index;
        }
        public int getValue() {
            return mIndex;
        }
    }

    public static final float sBaseColors[][] = {
        { 1, 0, 1, 1 },    // DIAMOND MAGENTA
        { 0, 1, 1, 1 },    // DIAMOND CYAN
        { 0, 0, 1, 1 },    // DIAMOND BLUE
        { 0, 1, 0, 1 },    // DIAMOND GREEN
        { 1, 1, 0, 1 },    // DIAMOND YELLOW
        { 1, 0.5f, 0, 1 }, // DIAMOND ORANGE
        { 1, 0, 0, 1 },    // DIAMOND RED
        { 1, 1, 1, 1 },    // DIAMONG WHITE
    };

    private boolean mUseLighting = false;
    /** Use texture is a bit field: 1=use texture name 1 w/ TMU0, 2=use texture name 2 w/ TMU1etc */
	private int mUseTextureUnit = 0;
	private boolean mEnabled = true;

    // These fields are protected so they can be used in unit tests
    protected ShortBuffer xIndexBuffer;
    protected IntBuffer   xVertexBuffer;
    protected IntBuffer   xNormalBuffer;
    protected IntBuffer   xDiffuseBuffer;
    protected IntBuffer   xTexCoordBuffer;

    /**
     * Constructs a new Simple3dSprite.
     */
    public Shape() {
    }

    /** Use texture is a bit field: 1=use texture unit 1 w/ TMU0, 2=use texture unit 2 w/ TMU1etc */
	public void setUseTextureUnit(int textureUnitIndex) {
		mUseTextureUnit = textureUnitIndex;
	}

	public void setUseLighting(boolean useLighting) {
		mUseLighting = useLighting;
	}

	public void setEnable(boolean enabled) {
		mEnabled = enabled;
	}

	/** 
     * Draws the 3d sprite into the given GL context. It's up to the caller
     * to perform rotations and translations as needed.
     */
    public void drawTo(GL10 gl) {
    	if (!mEnabled) {
    		return;
    	}

    	gl.glFrontFace(GL10.GL_CW);

        if (!mUseLighting) {
        	// gl.glColorPointer(4, GL10.GL_FIXED, 0, xDiffuseBuffer);
        	gl.glColor4x(xDiffuseBuffer.get(), xDiffuseBuffer.get(), xDiffuseBuffer.get(), xDiffuseBuffer.get());
        	xDiffuseBuffer.position(0);
        }

        if ((mUseTextureUnit & 1) != 0) {
            gl.glActiveTexture(GL10.GL_TEXTURE0); // TMU0
            gl.glTexCoordPointer(2 /* 2 coords per vertex */, GL10.GL_FIXED, 0, xTexCoordBuffer);
        }
        if ((mUseTextureUnit & 2) != 0) {
            gl.glActiveTexture(GL10.GL_TEXTURE1); // TMU1
            gl.glTexCoordPointer(2 /* 2 coords per vertex */, GL10.GL_FIXED, 0, xTexCoordBuffer);
        }
        
        synchronized(xVertexBuffer) {
        	gl.glVertexPointer(3 /* 3 coords per vertex */, GL10.GL_FIXED, 0, xVertexBuffer);
        }
        
        if (mUseLighting) {
        	assert xNormalBuffer != null;
        	assert xDiffuseBuffer != null;
	        gl.glNormalPointer(GL10.GL_FIXED, 0, xNormalBuffer);
	        gl.glMaterialxv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, xDiffuseBuffer);
	        gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 100.0f);
        }
        gl.glDrawElements(GL10.GL_TRIANGLES, xIndexBuffer.limit(),
                        GL10.GL_UNSIGNED_SHORT, xIndexBuffer);
    }

    /**
     * Setup the NIO buffers holding the geometry and color information
     * from regular float arrays. Derived classes must call this once in their
     * constructor.
     * 
     * @param vertices Vertices float array, (x,y,z) per vertex
     * @param normals Normals float array, one (x,y,z) per vertex
     * @param diffuse Diffuse color for all vertices. (r,g,b,a) tuplet.
     * @param indices List of triplets of vertices used to make CW triangles.
     */
    protected void setupBuffers(float[] vertices, float[] normals,
                    float[] diffuse, short[] indices) {
    	assert vertices != null;
    	assert indices != null;
    	if (vertices != null) xVertexBuffer = floatArrayToIntBuffer(vertices);
    	if (normals != null) xNormalBuffer = floatArrayToIntBuffer(normals);
    	if (diffuse != null) xDiffuseBuffer = floatArrayToIntBuffer(diffuse);

        xIndexBuffer = shortArrayToShortBuffer(indices);
    }

    protected void setupTextBuffers(float[] textCoord) {
		xTexCoordBuffer = floatArrayToIntBuffer(textCoord);
	}

    /**
     * Utility method that creates an NIO FloatBuffer out of a float[]
     * array. This is used by derived classes to prepare vertex and normal
     * buffers and is just made available here as a convenience method.
     */
    /*
    protected FloatBuffer floatArrayToFloatBuffer(final float[] floatArray) {
        // A float is 4 bytes
        ByteBuffer vbb = ByteBuffer.allocateDirect(floatArray.length*4);
        vbb.order(ByteOrder.nativeOrder());
        FloatBuffer buffer = vbb.asFloatBuffer();
        buffer.put(floatArray);
        buffer.position(0);
        return buffer;
    }
    */

    /**
     * Utility method that creates an NIO IntBuffer out of a float[]
     * array. This is used by derived classes to prepare vertex and normal
     * buffers and is just made available here as a convenience method.
     */
    protected IntBuffer floatArrayToIntBuffer(final float[] floatArray) {
        // An int is 4 bytes with 16/16 bits
        ByteBuffer vbb = ByteBuffer.allocateDirect(floatArray.length*4);
        vbb.order(ByteOrder.nativeOrder());
        IntBuffer buffer = vbb.asIntBuffer();
        for (float f : floatArray) {
            buffer.put((int)(f * 65536.0f));
        }
        buffer.position(0);
        return buffer;
    }

    protected ShortBuffer shortArrayToShortBuffer(final short[] shortArray) {
      // An int is 4 bytes with 16/16 bits
      ByteBuffer vbb = ByteBuffer.allocateDirect(shortArray.length*2);
      vbb.order(ByteOrder.nativeOrder());
      ShortBuffer buffer = vbb.asShortBuffer();
      buffer.put(shortArray);
      buffer.position(0);
      return buffer;
  }

    /**
     * Utility method to duplicate an int buffer.
     * Allocates a new buffer of the same size & native and copies all data
     * from one to the other.
     */
    protected IntBuffer duplicateIntBuffer(IntBuffer source) {
        ByteBuffer vbb = ByteBuffer.allocateDirect(source.limit() * 4);
        vbb.order(ByteOrder.nativeOrder());
        IntBuffer dest = vbb.asIntBuffer();
        source.position(0);
        dest.put(source);
        source.position(0);
        dest.position(0);
        return dest;
    }

    protected float[] getColor(Color color) {
        int offset = (color.getValue() - Color.MAGENTA.getValue());
        return sBaseColors[offset];
    }
}

